package engine;

import java.util.Random;
import static java.lang.Math.pow;
import static java.lang.Math.round;
import javax.swing.JTextArea;
import GUI.DynamicGraph;

/**
 * Środowisko
 * @author bejo
 */
public class Enviroment implements EnvInterface, Runnable {

    /** parametry funkcji celu **/
    static public int m1,  m2;
    /** parametry potegowej funkcji prawdopodobienstwa selekcji **/
    static public double a,  b;
    /** wielkosc elity ni **/
    static public int eliteSize;
    /** "prototyp" grafu **/
    static public byte[][] specimenPrototype;
    /** liczba genow (krawedzi) **/
    static public int genesNum;
    /** rozmiar populacji **/
    static public int populationSize;
    /** maksymalna ilosc iteracji, czyli pokolen (warunek stopu) **/
    static public int maxIterations;
    /** wystarczajaca liczba kolorow (warunek stopu) **/
    static public int goodEnough;
    // interakcje z GUI
    /**
     * Komunikacja z GUI - tekst
     */
    static public JTextArea txtOutput;
    /**
     * Komunikacja z GUI - graf 
     */
    static public DynamicGraph graph;
    /**
     * Wątek do obliczeń
     */
    static public Thread guiThread;
    /** maksymalna ilosc kolorow **/
    static public byte maxColors;
    /**
     * Generator wartość pseudolosowych na potrzeby algorytmu genetycznego
     */
    static public Random random = new Random();
    private Population basePopulation;
    private Population parentPopulation;
    private Population newPopulation;
    private float statsMin,  statsMax,  statsAvg;
    static private boolean running = false;

    /** konstruktor klasy Enviroment oblicza liczbe genow oraz tworzy populacje */
    public Enviroment() {
        maxColors = (byte) specimenPrototype.length;
        for (int dimX = 0; dimX < this.specimenPrototype[0].length; dimX++) {
            for (int dimY = dimX + 1; dimY < this.specimenPrototype[0].length; dimY++) {
                if (this.specimenPrototype[dimX][dimY] != 0) {
                    genesNum++;
                }
            }
        }
        basePopulation = new Population(populationSize);
        parentPopulation = new Population(0);
        newPopulation = new Population(0);
        refreshStats();
    }

    /** Wywolanie tej metody rozpoczyna "zycie" w naszym srodowisku bedacym osobym watkiem programu */
    public void run() {
        running = true;
        txtOutput.setText("ITERACJA: MIN / AVG / MAX");
        for (int i = 0; i < maxIterations; i++) {
            selection();
            reproduction();
            succession();
            refreshStats();
            graph.pushValue(statsMin, statsAvg, statsMax);
            txtOutput.setText(txtOutput.getText() + "\n" + i + ": " + statsMin + " / " + statsAvg + " / " + statsMax);
            txtOutput.setCaretPosition(txtOutput.getText().length());
            if (statsMin <= goodEnough) {
                break;
            }
        }
        txtOutput.setText(txtOutput.getText() + "\n\n" + "Algorytm zakonczony.\nNajlepszy osobnik ma wartosc funkcji celu: " + statsMin);
        txtOutput.setCaretPosition(txtOutput.getText().length());
        running = false;
        guiThread.start();
    }

    /** Zwraca najlepszego osobnika obecnej populacji bazowej
     * @return gatunek
     */
    public Specimen getBestSpecimen() {
        return basePopulation.getSpecimen(0);
    }

    /** Zwraca True jesli watek wciarz pracuje
     * @return stan wątku  z obliczeniami
     */
    public static synchronized boolean isRunning() {
        return running;
    }

    /** Ustawia zmienna running
     * @param running true - aktualnie pracuje, false - nie pracuje
     */
    public static void setRunning(boolean running) {
        Enviroment.running = running;
    }

    /** Oblicza nowe statystyki dla populacji bazowej */
    private void refreshStats() {
        statsAvg = 0;
        for (int i = 0; i < populationSize; i++) {
            statsAvg += basePopulation.getSpecimen(i).getAdaptation();
        }
        statsAvg /= populationSize;
        statsMin = basePopulation.getSpecimen(0).getAdaptation();
        statsMax = basePopulation.getSpecimen(populationSize - 1).getAdaptation();
    }

    /** Wybiera osobniki rodzicielskie z populacji bazowej */
    private void selection() {
        int i, j;
        basePopulation.sort();

        int[] r = new int[populationSize];                          //nadanie rang osobnikom

        for (i = 1; i < populationSize; i++) {
            if (basePopulation.getSpecimen(i).getAdaptation() == basePopulation.getSpecimen(i - 1).getAdaptation()) {
                r[i] = r[i - 1];
            } else {
                r[i] = r[i - 1] + 1;
            }
        }

        double tmp = 0;                                             //obliczenie wspolczynnika k dla obencej generacji

        for (i = 0; i < populationSize; i++) {
            tmp += pow(r[populationSize - 1] - r[i], b);
        }
        double k = (1 - ((double) populationSize * a)) / tmp;

        double[] distribution = new double[populationSize];         //obliczenie dystrybuanty

        distribution[0] = a + k * pow((r[populationSize - 1] - r[0]), b);
        for (i = 1; i < populationSize; i++) {
            distribution[i] = distribution[i - 1] + a + k * pow((r[populationSize - 1] - r[i]), b);
            if (distribution[i] < distribution[i - 1]) {
                System.err.println("UJEMNE PRAWDOPODOBIENSTWO!");
            }    //TODO: jakies ograniczenie 'a' wprowadzic mozna zeby to nie bylo potrzebne

        }
        distribution[populationSize - 1] = 1d; //zabezpieczenie przed bledami numerycznymi

        for (i = 0; i < populationSize; i++) {
            tmp = random.nextDouble();
            for (j = 0; j < populationSize; j++) {
                if (tmp <= distribution[j]) {
                    break;
                }
            }
            basePopulation.getSpecimen(j).optimizeColors();
            parentPopulation.addSpecimen(basePopulation.getSpecimen(j));
        }
    }

    /** Rozmnaza osobniki z populacji rodzicielskiej do nowej populacji */
    private void reproduction() {
        int r1, r2, cnt = parentPopulation.count();

        for (int i = 0; i < populationSize; i += 2) {
            newPopulation.crossOver(parentPopulation.getSpecimen(i), parentPopulation.getSpecimen(i + 1));
        }
    }

    /** Stworzenie nowej populacji bazowej */
    private void succession() {
        for (int i = 0; i < eliteSize; i++) //przeniesienie najlepszych osobnik�w (elity) z populacji bazowej do potomnej, bez sortowania bo w sortujemy w selekcji populacje bazowa juz
        {
            newPopulation.addSpecimen(basePopulation.getSpecimen(i));
        }

        basePopulation.clear();                 //zniszczenie starej populacji bazowej

        newPopulation.sort();
        for (int i = 0; i < populationSize; i++) //zbudowanie nowej populacji bazowej
        {
            basePopulation.addSpecimen(newPopulation.getSpecimen(i));
        }

        newPopulation.clear();                  //zniszczenie niepotrzebnych populacji

        parentPopulation.clear();
    }
}
